﻿using NLua;
using ScriptEngine.Script.Extension;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace ScriptEngine.Script.Util
{
	public static class LuaScriptObjectTransformUtil
	{
		private static bool IsBaseType(Type type)
		{
			return type.IsPrimitive || type == typeof(string) || type == typeof(object);
		}

		public static object TransformTo(this LuaTable srcObj, Type type)
		{
			ConstructorInfo constructor = type.GetConstructor(new Type[0]);
			if (constructor == null)
			{
				throw new Exception($"待转换的对象;{type},必须存在一个空参构造函数");
			}

			object targetObject = constructor.Invoke(new object[0]);

			foreach (PropertyInfo propertyInfo in type.GetProperties())
			{
				ScriptObjectPropertyAttribute scriptObjectProperty = propertyInfo.GetCustomAttribute<ScriptObjectPropertyAttribute>();
				if (scriptObjectProperty == null)
				{
					continue;
				}

				object srcProperty = srcObj[scriptObjectProperty.ObjectNameInScriptObject];
				if (scriptObjectProperty.Require && srcProperty == null)
				{
					throw new NullReferenceException($"未在脚本对象中找到必要属性:{scriptObjectProperty.ObjectNameInScriptObject}");
				}

				if (srcProperty == null && !scriptObjectProperty.Require)
				{
					propertyInfo.SetValue(targetObject, scriptObjectProperty.DefaultValue);
					continue;
				}

				if (IsBaseType(propertyInfo.PropertyType))
				{
					propertyInfo.SetValue(targetObject, Convert.ChangeType(srcProperty, propertyInfo.PropertyType));
				}
				else
				{
					if (propertyInfo.PropertyType.IsArray)
					{
						Type targetElementType = propertyInfo.PropertyType.GetElementType();
						if (targetElementType.IsGenericType)
						{
							throw new Exception($"不支持的数组类型: {targetElementType}");
						}

						if (srcProperty == null)
						{
							propertyInfo.SetValue(targetObject, null);
							continue;
						}

						if (srcProperty.GetType() != typeof(LuaTable))
						{
							throw new Exception($"目标对象:{propertyInfo.Name}的类型为数组类型，但是脚本对象非数组类型");
						}

						LinkedList<object> objects = new LinkedList<object>();
						(srcProperty as LuaTable).ForEachAsArray((idx, val) =>
						{
							objects.AddLast(val);
							return true;
						});
						object[] srcPropArr = objects.ToArray();

						Array targetArr = Array.CreateInstance(targetElementType, srcPropArr.Length);
						for (var i = 0; i < srcPropArr.Length; i++)
						{
							if (IsBaseType(targetElementType))
							{
								targetArr.SetValue(Convert.ChangeType(srcPropArr[i], targetElementType), i);
							}
							else if (srcPropArr[i] is LuaTable srcPropObj)
							{
								targetArr.SetValue(srcPropObj.TransformTo(targetElementType), i);
							}
							else
							{
								throw new Exception($"不支持的类型转换: [{srcPropArr[i].GetType()}] to [{targetElementType}]");
							}
						}

						propertyInfo.SetValue(targetObject, targetArr);
					}
					else if (srcProperty is LuaTable srcPropObj)
					{
						if (propertyInfo.PropertyType.IsAssignableFrom(typeof(ExpandoObject)))
						{
							IDictionary<string, object> srcDict = new ExpandoObject();
							srcPropObj.ForEach((k, v) =>
							{
								srcDict[k.ToString()] = v;
								return true;
							});
							propertyInfo.SetValue(targetObject, srcDict);
						}
						else
						{
							propertyInfo.SetValue(targetObject, srcPropObj.TransformTo(propertyInfo.PropertyType));
						}
					}
					else
					{
						throw new Exception($"不支持的转换类型: [{srcProperty.GetType()}] to [{propertyInfo.PropertyType}]");
					}
				}
			}

			return targetObject;
		}

		public static T TransformTo<T>(this LuaTable obj)
		{
			return (T)obj.TransformTo(typeof(T));
		}
	}
}
